// irrp01.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <irrlicht.h>
#include <iostream>
#include "Recast.h"
#include <map>
#include <list>

#include "MyEventReceiver.h"

typedef std::list<irr::u16> facelist;
typedef std::map<irr::u16,facelist*> facemap;

using namespace irr;

#ifdef _MSC_VER
#pragma comment(lib, "Irrlicht.lib")
#endif

IrrlichtDevice *g_device;
video::IVideoDriver* g_driver;
scene::ISceneManager* g_smgr;

SAppContext* context = NULL;

float cellsize = 40;
float cellheight = 40;
float agentheight = 25;
float agentmaxclimb = 100;
float agentradius = 5;
float maxedgelen = 30;
float minregionsize = 10;
float mergeregionsize = 5;
int maxvertsperpoly = 3;
float detailsampledist = 0.6f;
float detailsamplemaxerror = 0.1f;

scene::IAnimatedMesh* mesh = NULL;
scene::SMesh* navmesh = NULL;
scene::ISceneNode* navmeshnode = NULL;
scene::ICameraSceneNode* camera = NULL;
scene::ICameraSceneNode* quakecamera = NULL;

gui::IGUIEditBox* edit_cellsize = NULL;
gui::IGUIEditBox* edit_cellheight = NULL;
gui::IGUIEditBox* edit_agentheight = NULL;
gui::IGUIEditBox* edit_agentmaxclimb = NULL;
gui::IGUIEditBox* edit_agentradius = NULL;
gui::IGUIEditBox* edit_maxedgelen = NULL;
gui::IGUIEditBox* edit_minregionsize = NULL;
gui::IGUIEditBox* edit_mergeregionsize = NULL;
gui::IGUIEditBox* edit_maxvertsperpoly = NULL;
gui::IGUIEditBox* edit_detailsampledist = NULL;
gui::IGUIEditBox* edit_detailsamplemaxerror = NULL;


MyEventReceiver* receiver;


void rcirrVcopy(float * v1,const core::vector3df v2)
{
	v1[0] = v2.X;
	v1[1] = v2.Y;
	v1[2] = v2.Z;
}

void rcirrVcopy(core::vector3df* v1,const float* v2)
{
	v1->X = v2[0];
	v1->Y = v2[1];
	v1->Z = v2[2];
}

bool convertNavMesh(rcPolyMeshDetail* in, scene::SMesh* out) 
{
	scene::CMeshBuffer<video::S3DVertex>* meshbuff = new scene::CMeshBuffer<video::S3DVertex>();
	out->addMeshBuffer(meshbuff);
	core::vector3df normal(0, 0, 1.0f);
	video::SColor color(255, 200, 50, 100);
	core::vector2df uv(0, 0);
	video::SMaterial mat;
	mat.AmbientColor = color;
	mat.Lighting = false;
	meshbuff->Material = mat;
	irr::u16 verti = 0;

	for (int i = 0; i < in->nmeshes; i++)
	{
		int basev = in->meshes[4 * i + 0];
		int numv = in->meshes[4 * i + 1];
		int baset = in->meshes[4 * i + 2];
		int numt = in->meshes[4 * i + 3];
		for (int j = 0; j < numt; j++)
		{
			const unsigned char* t = &in->tris[(baset+j)*4];

			for (int k = 0; k < 3; k++)
			{
				const float* v = &in->verts[(basev+t[k])*3];
				core::vector3df v3df;
				rcirrVcopy(&v3df, v);
				video::S3DVertex vertex(v3df, normal, color, uv);
				meshbuff->Vertices.push_back(vertex);
				meshbuff->Indices.push_back(verti++);
			}
		}
	}

	
	return true;
}

bool createNavMesh(scene::IMesh* mesh, scene::SMesh* out)
{
	rcContext* ctx = new rcContext();
	rcConfig cfg;
	cfg.cs = cellsize;
	cfg.ch = cellheight;
	cfg.walkableSlopeAngle = 45;
	cfg.walkableHeight = (int)ceilf(agentheight / cfg.ch);
	cfg.walkableClimb = (int)floorf(agentmaxclimb / cfg.ch);
	cfg.walkableRadius = (int)ceilf(agentradius / cfg.cs);
	cfg.maxEdgeLen = (int)(maxedgelen / cfg.cs);
	cfg.maxSimplificationError = 1;
	cfg.minRegionArea = (int)rcSqr(minregionsize);
	cfg.mergeRegionArea = (int)rcSqr(mergeregionsize);
	cfg.maxVertsPerPoly = maxvertsperpoly;
	cfg.detailSampleDist = detailsampledist * cfg.cs;
	cfg.detailSampleMaxError = cfg.ch * detailsamplemaxerror;

	core::aabbox3df bbox = mesh->getBoundingBox();
	core::vector3df min = bbox.MinEdge;
	core::vector3df max = bbox.MaxEdge;

	int* faces;
	irr::u8* areas;
	float* verts;

	unsigned int i,j;
	unsigned int nverts = 0;
	unsigned int npolys = 0;
	
	for (i = 0; i < mesh->getMeshBufferCount(); i++) 
	{
		scene::IMeshBuffer* mb = mesh->getMeshBuffer(i);
		nverts += mb->getVertexCount();
		npolys += mb->getIndexCount();
	}

	verts = new float [nverts * 3];

	npolys /= 3;
	
	faces = new int [npolys * 3];
	areas = new irr::u8 [npolys];
	memset(areas, 0, npolys * sizeof(irr::u8));

	int faceidbase = 0;
	int vertexbase = 0;
	
	/*
	facemap fmap;
	facemap::iterator fmapi;

	for (i = 0; i < mesh->getMeshBufferCount(); i++) 
	{
		scene::IMeshBuffer* mb = mesh->getMeshBuffer(i);
		irr::u16* indices = mb->getIndices();
		unsigned int indexcount = mb->getIndexCount();

		for (j = 0; j < indexcount; j++)
		{
			irr::u16 faceid = faceidbase + (j / 3);
			irr::u16 vertex = vertexbase + indices[j];
			
			facelist* flist = fmap[vertex];	
			if (flist == NULL)
			{
				flist = new facelist();
				fmap[vertex] = flist;
			}
			flist->push_back(faceid);
			
			faces[(faceid * 6) + (j % 3)] = vertex;
			regs[faceid] = i;
		}

		faceidbase += indexcount / 3;
		vertexbase += mb->getVertexCount();
	}

	for (i = 0; i < npolys; i++) 
	{
		for (j = 0; j < 3; j++)
		{
			irr::u16 vertex1 = faces[(i * 6) + j];
			irr::u16 vertex2 = faces[(i * 6) + ((j + 1) % 3)];
			facelist* flist1 = fmap[vertex1];
			facelist* flist2 = fmap[vertex2];
			facelist::iterator flist1it;
			facelist::iterator flist2it;
			bool found = false;
			irr::u16 flist1face;
			irr::u16 flist2face;
			for (flist1it = flist1->begin(); !found && flist1it!=flist1->end(); flist1it++)
			{
				flist1face = *flist1it;
				for (flist2it = flist2->begin(); (i != flist1face) && !found && (flist2it != flist2->end()); flist2it++)
				{
					flist2face = *flist2it;
					if (flist1face == flist2face)
					{
						found = true;
					}
				}
			}
			if (found)
			{
				faces[(i * 6) + 3 + j] = flist1face;
			}
			else
			{
				faces[(i * 6) + 3 + j] = RC_MESH_NULL_IDX;
			}
		}
	}
	*/

	faceidbase = 0;
	vertexbase = 0;
	for (i = 0; i < mesh->getMeshBufferCount(); i++) 
	{
		scene::IMeshBuffer* mb = mesh->getMeshBuffer(i);
		irr::u16* indices = mb->getIndices();

		for (j = 0; j < mb->getIndexCount(); j++)
		{
			int faceid = faceidbase + j / 3;
			int vertex = vertexbase + indices[j];
			faces[faceid * 3 + (j % 3)] = vertex;
		}

		for (j = 0; j < mb->getVertexCount(); j++)
		{
			int vertex = vertexbase + j;
			core::vector3df vect = mb->getPosition(j);
			//vect -= min;
			rcirrVcopy(&verts[vertex * 3],vect);
		}

		vertexbase += mb->getVertexCount();
		faceidbase += mb->getIndexCount() / 3;
	}

	rcirrVcopy(cfg.bmin, min);
	rcirrVcopy(cfg.bmax, max);
	rcCalcGridSize(cfg.bmin, cfg.bmax, cfg.cs, &cfg.width, &cfg.height);

	rcHeightfield* solid = rcAllocHeightfield();
	if (!solid)
	{
		printf("solid allocation failed\n");
		return false;
	}
	
	if (!rcCreateHeightfield(ctx, *solid, cfg.width, cfg.height, cfg.bmin, cfg.bmax, cfg.cs, cfg.ch))
	{
		printf("solid creation failed\n");
		return false;
	}

	rcMarkWalkableTriangles(ctx, cfg.walkableSlopeAngle, verts, nverts, faces, npolys, areas);
	rcRasterizeTriangles(ctx, verts, nverts, faces, areas, npolys, *solid, cfg.walkableClimb);
	
	delete [] areas;
	areas = NULL;
	
	rcFilterLowHangingWalkableObstacles(ctx, cfg.walkableClimb, *solid);
	rcFilterLedgeSpans(ctx, cfg.walkableHeight, cfg.walkableClimb, *solid);
	rcFilterWalkableLowHeightSpans(ctx, cfg.walkableHeight, *solid);

	rcCompactHeightfield* chf = rcAllocCompactHeightfield();

	if (!chf)
	{
		printf("compact heightfield allocation failed\n");
		return false;
	}

	if (!rcBuildCompactHeightfield(ctx, cfg.walkableHeight, cfg.walkableClimb, *solid, *chf))
	{
		printf("could not build compact data\n");
		return false;
	}

	rcFreeHeightField(solid);
	solid = NULL;

	if (!rcErodeWalkableArea(ctx, cfg.walkableRadius, *chf))
	{
		printf("could not erode\n");
		return false;
	}

	if (!rcBuildDistanceField(ctx, *chf))
	{
		printf("could not build distance field\n");
		return false;
	}

	if (!rcBuildRegions(ctx, *chf, cfg.borderSize, cfg.minRegionArea, cfg.mergeRegionArea))
	{
		printf("could not build regions\n");
		return false;
	}

	rcContourSet* cset = rcAllocContourSet();

	if (!cset)
	{
		printf("could not alloc contour set\n");
		return false;
	}

	if (!rcBuildContours(ctx, *chf, cfg.maxSimplificationError, cfg.maxEdgeLen, *cset))
	{
		printf("could not create contours\n");
		return false;
	}

	rcPolyMesh* pmesh = rcAllocPolyMesh();

	if (!pmesh)
	{
		printf("could not alloc poly mesh\n");
		return false;
	}

	if (!rcBuildPolyMesh(ctx, *cset, cfg.maxVertsPerPoly, *pmesh))
	{
		printf("could not triangulate contours\n");
		return false;
	}
	
	rcPolyMeshDetail* dmesh = rcAllocPolyMeshDetail();

	if (!dmesh)
	{
		printf("could not alloc detail mesh\n");
		return false;
	}

	if (!rcBuildPolyMeshDetail(ctx, *pmesh, *chf, cfg.detailSampleDist, cfg.detailSampleMaxError, *dmesh))
	{
		printf("could not build detail mesh\n");
		return false;
	}

	rcFreeCompactHeightfield(chf);
	chf = NULL;
	rcFreeContourSet(cset);
	cset = NULL;

	convertNavMesh(dmesh, out);
	out->setDirty();
	out->recalculateBoundingBox();
	//out->setDebugName("navmesh");

	rcFreePolyMesh(pmesh);
	rcFreePolyMeshDetail(dmesh);
}

void compileNavmesh()
{
	if (navmesh)
	{
		navmeshnode->remove();
		delete navmesh;
	}

	navmesh = new scene::SMesh();

	createNavMesh(mesh->getMesh(0), navmesh);
	navmeshnode = g_smgr->addMeshSceneNode(navmesh);
	navmeshnode->setAutomaticCulling(scene::EAC_OFF);
}

void createGUI()
{
	SAppContext* context = new SAppContext();
	context->quakecam = true;
	context->device = g_device;

	gui::IGUIEnvironment* env = g_device->getGUIEnvironment();
	gui::IGUISkin* skin = env->getSkin();
	gui::IGUIFont* font = env->getFont("../../q3stuff/fonthaettenschweiler.bmp");
	if (font)
	{
		skin->setFont(font);
	}

	skin->setFont(env->getBuiltInFont(), EGDF_TOOLTIP);

	env->addButton(core::rect<s32>(500,400,600,420), 0, GUI_ID_COMPILE, L"Compile", L"Compiles Navmesh");

	gui::IGUIStaticText* back=env->addStaticText(L"", core::rect<s32>(450,100,640,380),true);
	back->setBackgroundColor(video::SColor(180,200,200,200));

	int y = 100;
	int h = 20;
	int hh = 2;
	int x1 = 450;
	int x2 = 600;
	int w1 = 150;
	int w2 = 30;
	env->addStaticText(L"cellsize",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_cellsize = env->addEditBox(L"40",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"cellheight",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_cellheight = env->addEditBox(L"40",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"agentheight",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_agentheight = env->addEditBox(L"25",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"agentmaxclimb",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_agentmaxclimb = env->addEditBox(L"100",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"agentradius",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_agentradius = env->addEditBox(L"5",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"maxedgelen",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_maxedgelen = env->addEditBox(L"30",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"minregionsize",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_minregionsize = env->addEditBox(L"10",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"mergeregionsize",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_mergeregionsize = env->addEditBox(L"5",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"maxvertsperpoly",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_maxvertsperpoly = env->addEditBox(L"3",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"detailsampledist",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_detailsampledist = env->addEditBox(L"0.6",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;
	env->addStaticText(L"detailsamplemaxerror",core::rect<s32>(x1, y, x1 + w1, y + h), false);
	edit_detailsamplemaxerror = env->addEditBox(L"0.1",core::rect<s32>(x2, y, x2 + w2, y + h), true);
	y+=h + hh;

	
	receiver = new MyEventReceiver(*context);
	g_device->setEventReceiver(receiver);
}


int _tmain(int argc, _TCHAR* argv[])
{
	g_device = createDevice(video::EDT_DIRECT3D9, core::dimension2d<u32>(640,480));
	if (g_device == NULL)
	{
		return 1;
	}

	g_driver = g_device->getVideoDriver();
	g_smgr = g_device->getSceneManager();

	createGUI();

	if (!g_device->getFileSystem()->addFileArchive("../../q3stuff/love.pk3"))
	{
		return 1;
	}

	mesh = g_smgr->getMesh("love.bsp");
	scene::ISceneNode* node = 0;

	if (mesh)
	{
		//node = g_smgr->addOctreeSceneNode(mesh);
		node = g_smgr->addAnimatedMeshSceneNode(mesh);
	}

	camera = g_smgr->addCameraSceneNode();
	g_smgr->addCameraSceneNodeFPS();
	quakecamera = g_smgr->getActiveCamera();

	while(g_device->run())
	{
		if (g_device->isWindowActive())
		{
			g_driver->beginScene(true, true, video::SColor(255, 20, 20, 20));
			g_smgr->drawAll();
			g_device->getGUIEnvironment()->drawAll();
			g_driver->endScene();
		}
		else
		{
			g_device->yield();
		}
	}

	g_device->drop();

	return 0;
}
